/*
 * @(#)HiddenTagView.java	1.15 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
*Released on 28th June 2008.
*Any violations can be reported at paritosh@wikiocean.net
*What is treated as violations can be found at www.wikiocean.net/ppl1-voilations*
******************************************************************************
* The contents of this file are subject to POOL Public License 1.0 and later.
* POOL Public License is based on Affero GPL v3 and additional conditions.
* ("License"); You may not use this file except in compliance with the License
* You may obtain a copy of the Affero GPL v3 License at http://www.gnu.org/licenses/agpl.html
* You may obtain a copy of the POOL Public License 1.0 or later at www.wikiocean.net/license/ppl.html
* Software distributed under POOL Public License 1.0 is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
* The Initial Developer of the Original Code is Paritosh Pungaliya (C) 2008. All Rights Reserved.
******************************************************************************
* Objective of the additional terms (license)
* 1) Is to extend the software freedom to freedom to work.
* 2) To ensure that knowledge is free from monopoly of agencies.
* 3) To avoid a situation where big corporate or investor can buy out free software groups and companies and then start milking the communities built around it.
* (this trend can be seen how the freedom is curtailed in companies that get Venture Capital funding.)
******************************************************************************
*/

package  guiparser;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.text.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.util.*;

/**
 * HiddenTagView subclasses EditableView to contain a JTextField showing
 * the element name. When the textfield is edited the element name is
 * reset. As this inherits from EditableView if the JTextComponent is
 * not editable, the textfield will not be visible.
 *
 * @author  Scott Violet
 * @version 1.15, 12/19/03
 */
class HiddenTagView extends EditableView implements DocumentListener {
    HiddenTagView(Element e) {
	super(e);
	yAlign = 1;
    }

    protected Component createComponent() {
	JTextField tf = new JTextField(getElement().getName());
	Document doc = getDocument();
	Font font;
	if (doc instanceof StyledDocument) {
	    font = ((StyledDocument)doc).getFont(getAttributes());
	    tf.setFont(font);
	}
	else {
	    font = tf.getFont();
	}
	tf.getDocument().addDocumentListener(this);
	updateYAlign(font);

	// Create a panel to wrap the textfield so that the textfields
	// laf border shows through.
	JPanel panel = new JPanel(new BorderLayout());
	panel.setBackground(null);
	if (isEndTag()) {
	    panel.setBorder(EndBorder);
	}
	else {
	    panel.setBorder(StartBorder);
	}
	panel.add(tf);
	return panel;
    }

    public float getAlignment(int axis) {
	if (axis == View.Y_AXIS) {
	    return yAlign;
	}
	return 0.5f;
    }

    public float getMinimumSpan(int axis) {
	if (axis == View.X_AXIS && isVisible()) {
	    // Default to preferred.
	    return Math.max(30, super.getPreferredSpan(axis));
	}
	return super.getMinimumSpan(axis);
    }

    public float getPreferredSpan(int axis) {
	if (axis == View.X_AXIS && isVisible()) {
	    return Math.max(30, super.getPreferredSpan(axis));
	}
	return super.getPreferredSpan(axis);
    }

    public float getMaximumSpan(int axis) {
	if (axis == View.X_AXIS && isVisible()) {
	    // Default to preferred.
	    return Math.max(30, super.getMaximumSpan(axis));
	}
	return super.getMaximumSpan(axis);
    }

    // DocumentListener methods
    public void insertUpdate(DocumentEvent e) {
	updateModelFromText();
    }

    public void removeUpdate(DocumentEvent e) {
	updateModelFromText();
    }

    public void changedUpdate(DocumentEvent e) {
	updateModelFromText();
    }

    // View method
    public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
	if (!isSettingAttributes) {
	    setTextFromModel();
	}
    }

    // local methods

    void updateYAlign(Font font) {
        Container c = getContainer();
	FontMetrics fm = (c != null) ? c.getFontMetrics(font) :
            Toolkit.getDefaultToolkit().getFontMetrics(font);
	float h = fm.getHeight();
	float d = fm.getDescent();
	yAlign = (h - d) / h;
    }

    void resetBorder() {
	Component comp = getComponent();

	if (comp != null) {
	    if (isEndTag()) {
		((JPanel)comp).setBorder(EndBorder);
	    }
	    else {
		((JPanel)comp).setBorder(StartBorder);
	    }
	}
    }

    /**
     * This resets the text on the text component we created to match
     * that of the AttributeSet for the Element we represent.
     * <p>If this is invoked on the event dispatching thread, this
     * directly invokes <code>_setTextFromModel</code>, otherwise
     * <code>SwingUtilities.invokeLater</code> is used to schedule execution
     * of <code>_setTextFromModel</code>.
     */
    void setTextFromModel() {
	if (SwingUtilities.isEventDispatchThread()) {
	    _setTextFromModel();
	}
	else {
	    SwingUtilities.invokeLater(new Runnable() {
		public void run() {
		    _setTextFromModel();
		}
	    });
	}
    }

    /**
     * This resets the text on the text component we created to match
     * that of the AttributeSet for the Element we represent.
     */
    void _setTextFromModel() {
	Document doc = getDocument();
	try {
	    isSettingAttributes = true;
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readLock();
	    }
	    JTextComponent text = getTextComponent();
	    if (text != null) {
		text.setText(getRepresentedText());
		resetBorder();
		Container host = getContainer();
		if (host != null) {
		    preferenceChanged(this, true, true);
		    host.repaint();
		}
	    }
	}
	finally {
	    isSettingAttributes = false;
	    if (doc instanceof AbstractDocument) {
		((AbstractDocument)doc).readUnlock();
	    }
	}
    }

    /**
     * This copies the text from the text component we've created
     * to the Element's AttributeSet we represent.
     * <p>If this is invoked on the event dispatching thread, this
     * directly invokes <code>_updateModelFromText</code>, otherwise
     * <code>SwingUtilities.invokeLater</code> is used to schedule execution
     * of <code>_updateModelFromText</code>.
     */
    void updateModelFromText() {
	if (!isSettingAttributes) {
	    if (SwingUtilities.isEventDispatchThread()) {
		_updateModelFromText();
	    }
	    else {
		SwingUtilities.invokeLater(new Runnable() {
		    public void run() {
			_updateModelFromText();
		    }
		});
	    }
	}
    }

    /**
     * This copies the text from the text component we've created
     * to the Element's AttributeSet we represent.
     */
    void _updateModelFromText() {
	Document doc = getDocument();
	Object name = getElement().getAttributes().getAttribute
	    (StyleConstants.NameAttribute);
	if ((name instanceof HTML.UnknownTag) &&
	    (doc instanceof StyledDocument)) {
	    SimpleAttributeSet sas = new SimpleAttributeSet();
	    JTextComponent textComponent = getTextComponent();
	    if (textComponent != null) {
		String text = textComponent.getText();
		isSettingAttributes = true;
		try {
		    sas.addAttribute(StyleConstants.NameAttribute,
				     new HTML.UnknownTag(text));
		    ((StyledDocument)doc).setCharacterAttributes
			(getStartOffset(), getEndOffset() -
			 getStartOffset(), sas, false);
		}
		finally {
		    isSettingAttributes = false;
		}
	    }
	}
    }

    JTextComponent getTextComponent() {
	Component comp = getComponent();

	return (comp == null) ? null : (JTextComponent)((Container)comp).
	                               getComponent(0);
    }

    String getRepresentedText() {
	String retValue = getElement().getName();
	return (retValue == null) ? "" : retValue;
    }

    boolean isEndTag() {
	AttributeSet as = getElement().getAttributes();
	if (as != null) {
	    Object end = as.getAttribute(HTML.Attribute.ENDTAG);
	    if (end != null && (end instanceof String) &&
		((String)end).equals("true")) {
		return true;
	    }
	}
	return false;
    }

    /** Alignment along the y axis, based on the font of the textfield. */
    float yAlign;
    /** Set to true when setting attributes. */
    boolean isSettingAttributes;


    // Following are for Borders that used for Unknown tags and comments.
    //
    // Border defines
    static final int circleR = 3;
    static final int circleD = circleR * 2;
    static final int tagSize = 6;
    static final int padding = 3;
    static final Color UnknownTagBorderColor = Color.black;
    static final Border StartBorder = new StartTagBorder();
    static final Border EndBorder = new EndTagBorder();


    static class StartTagBorder implements Border, Serializable {
	public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height) {
	    g.setColor(UnknownTagBorderColor);
	    x += padding;
	    width -= (padding * 2);
	    g.drawLine(x, y + circleR,
		       x, y + height - circleR);
	    g.drawArc(x, y + height - circleD - 1,
		      circleD, circleD, 180, 90);
	    g.drawArc(x, y, circleD, circleD, 90, 90);
	    g.drawLine(x + circleR, y, x + width - tagSize, y);
	    g.drawLine(x + circleR, y + height - 1,
		       x + width - tagSize, y + height - 1);
	    
	    g.drawLine(x + width - tagSize, y,
		       x + width - 1, y + height / 2);
	    g.drawLine(x + width - tagSize, y + height,
		       x + width - 1, y + height / 2);
	}

	public Insets getBorderInsets(Component c) {
	    return new Insets(2, 2 + padding, 2, tagSize + 2 + padding);
	}

	public boolean isBorderOpaque() {
	    return false;
	}
    } // End of class HiddenTagView.StartTagBorder


    static class EndTagBorder implements Border, Serializable {
	public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height) {
	    g.setColor(UnknownTagBorderColor);
	    x += padding;
	    width -= (padding * 2);
	    g.drawLine(x + width - 1, y + circleR,
		       x + width - 1, y + height - circleR);
	    g.drawArc(x + width - circleD - 1, y + height - circleD - 1,
		      circleD, circleD, 270, 90);
	    g.drawArc(x + width - circleD - 1, y, circleD, circleD, 0, 90);
	    g.drawLine(x + tagSize, y, x + width - circleR, y);
	    g.drawLine(x + tagSize, y + height - 1,
		       x + width - circleR, y + height - 1);
	    
	    g.drawLine(x + tagSize, y,
		       x, y + height / 2);
	    g.drawLine(x + tagSize, y + height,
		       x, y + height / 2);
	}

	public Insets getBorderInsets(Component c) {
	    return new Insets(2, tagSize + 2 + padding, 2, 2 + padding);
	}

	public boolean isBorderOpaque() {
	    return false;
	}
    } // End of class HiddenTagView.EndTagBorder


} // End of HiddenTagView
